package com.hjx.other;

import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;

/**
 * # Challenge
 * The nation of Examplania has the following income tax brackets:
 * ```
 * income cap      marginal tax rate
 *   ¤10,000           0.00 (0%)
 *   ¤30,000           0.10 (10%)
 *   ¤100,000          0.25 (25%)
 *     --              0.40 (40%)
 * ```
 * If you're not familiar with how tax brackets work, see the section below for an explanation.
 *
 * Given a whole-number income amount up to ¤100,000,000, find the amount of tax owed in Examplania. Round down to a whole number of ¤.
 *
 * # Examples
 * ```
 * tax(0) => 0
 * tax(10000) => 0
 * tax(10009) => 0
 * tax(10010) => 1
 * tax(12000) => 200
 * tax(56789) => 8697
 * tax(1234567) => 473326
 * ```
 * # Optional improvement
 * One way to improve your code is to make it easy to swap out different tax brackets, for instance by having the table in an input file. If you do this, you may assume that both the income caps and marginal tax rates are in increasing order, the highest bracket has no income cap, and all tax rates are whole numbers of percent (no more than two decimal places).
 *
 * However, because this is an Easy challenge, this part is optional, and you may hard code the tax brackets if you wish.
 *
 * # How tax brackets work
 * A tax bracket is a range of income based on the income caps, and each tax bracket has a corresponding marginal tax rate, which applies to income within the bracket. In our example, the tax bracket for the range ¤10,000 to ¤30,000 has a marginal tax rate of 10%. Here's what that means for each bracket:
 *
 * *  If your income is less than ¤10,000, you owe 0 income tax.
 * * If your income is between ¤10,000 and ¤30,000, you owe 10% income tax on the income that exceeds ¤10,000. For instance, if your income is ¤18,000, then your income in the 10% bracket is ¤8,000. So your income tax is 10% of ¤8,000, or ¤800.
 * * If your income is between ¤30,000 and ¤100,000, then you owe 10% of your income between ¤10,000 and ¤30,000, plus 25% of your income over ¤30,000.
 * * And finally, if your income is over ¤100,000, then you owe 10% of your income from ¤10,000 to ¤30,000, plus 25% of your income from ¤30,000 to ¤100,000, plus 40% of your income above ¤100,000.
 * One aspect of progressive taxation is that increasing your income will never decrease the amount of tax that you owe, or your overall tax rate (except for rounding).
 *
 * # Optional bonus
 * The overall tax rate is simply the total tax divided by the total income. For example, an income of ¤256,250 has an overall tax of ¤82,000, which is an overall tax rate of exactly 32%:
 * ```
 * 82000 = 0.00 × 10000 + 0.10 × 20000 + 0.25 × 70000 + 0.40 × 156250
 * 82000 = 0.32 × 256250
 * ```
 * Given a target overall tax rate, find the income amount that would be taxed at that overall rate in Examplania:
 * ```
 * overall(0.00) => 0 (or anything up to 10000)
 * overall(0.06) => 25000
 * overall(0.09) => 34375
 * overall(0.32) => 256250
 * overall(0.40) => NaN (or anything to signify that no such income value exists)
 * ```
 * You may get somewhat different answers because of rounding, but as long as it's close that's fine.
 *
 * The simplest possibility is just to iterate and check the overall tax rate for each possible income. That works fine, but if you want a performance boost, check out binary search. You can also use algebra to reduce the number of calculations needed; just make it so that your code still gives correct answers if you swap out a different set of tax brackets.
 */
public class CalculatorTax {

    /**
     * 阶梯税率
     */
    public static final Map<String, BigDecimal> INCOME_CAP_TAX_RATE_MAPPINGS = new HashMap<String, BigDecimal>(){{
        put("<=10000", new BigDecimal(String.valueOf(0.00)));
        put(">10000 && <=30000", new BigDecimal(String.valueOf(0.10)));
        put(">30000 && <=100000", new BigDecimal(String.valueOf(0.25)));
        put(">100000", new BigDecimal(String.valueOf(0.40)));
    }};

    public static BigDecimal taxRate(String capTaxRate){
        return INCOME_CAP_TAX_RATE_MAPPINGS.get(capTaxRate);
    }

    /**
     * 速算扣除数
     */
    public static final Map<String, BigDecimal> FAST_CALCULATOR_FACTOR = new HashMap<String, BigDecimal>(){{
        put("<=10000", BigDecimal.ZERO);
        put(">10000 && <=30000", BigDecimal.ZERO);
        put(">30000 && <=100000", new BigDecimal("20000")
                .multiply(taxRate(">10000 && <=30000")));
        put(">100000", new BigDecimal("70000")
                .multiply(taxRate(">30000 && <=100000"))
                .add(new BigDecimal("20000")
                        .multiply(taxRate(">10000 && <=30000"))));
    }};

    public static BigDecimal fastValue(String capTaxRate) {
        return FAST_CALCULATOR_FACTOR.get(capTaxRate);
    }

    /**
     * 舍弃小数点后的数字，取整
     * @param toBeConverted 带转换值
     * @return 整数
     */
    public BigDecimal roundDown(BigDecimal toBeConverted){
        return toBeConverted.setScale(0, BigDecimal.ROUND_DOWN);
    };

    /**
     * 保留两位小数
     *
     * @param toBeConverted 待转换值
     * @return
     */
    public double twoScale(double toBeConverted){
        return new BigDecimal(String.valueOf(toBeConverted)).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
    };


    /**
     * 根据收入计算税费
     * （收入所在阶梯 - 上一阶梯的上限） * 本阶梯税率 = 本阶梯应税金额
     * 税费 = 总收入 - 收入所在阶梯应税金额 - 速算扣除数
     *
     * income cap      marginal tax rate
     *   ¤10,000           0.00 (0%)
     *   ¤30,000           0.10 (10%)
     *   ¤100,000          0.25 (25%)
     *     --              0.40 (40%)
     *
     * @param income 收入
     * @return 税费
     */
    public double tax(double income){

        BigDecimal bAmount = new BigDecimal(String.valueOf(income));
        BigDecimal gradeTax = BigDecimal.ZERO;
        if (income > 100000d) {
            BigDecimal diff100000 = bAmount.subtract(new BigDecimal("100000"));
            gradeTax = diff100000.multiply(taxRate(">100000"))
                    .add(fastValue(">100000"));

        } else if(income > 30000d) {
            BigDecimal diff30000 = bAmount.subtract(new BigDecimal("30000"));
            gradeTax = diff30000.multiply(taxRate(">30000 && <=100000"))
                    .add(fastValue(">30000 && <=100000"));

        } else if(income > 10000d) {
            BigDecimal diff10000 = bAmount.subtract(new BigDecimal("10000"));
            gradeTax = diff10000.multiply(taxRate(">10000 && <=30000"))
                    .add(fastValue(">10000 && <=30000"));
        }

        return roundDown(gradeTax).doubleValue();
    };

    public double getRate(double income) {
        return twoScale(tax(income) / income);
    }

    /**
     * 指定收入范围
     *
     * @param rate
     * @param lower
     * @param upper
     * @return
     */
    public double overallByBinarySearch(int lower, int upper, double rate){

        if(lower == 0d) {
            return 0d;
        }
        double lowerRate = getRate(lower);
        double upperRate = getRate(upper);

//        System.out.println("最低收入税占比 " +  lowerRate + " 最高收入税占比 " + upperRate);

        while (twoScale(lowerRate) < twoScale(upperRate)) {
            int middle = (lower + upper) / 2;
            double middleRate = getRate(middle);

            if (middleRate < rate) {
                lower = middle + 1;
                lowerRate = middleRate;

            } else if(middleRate > rate) {
                upper = middle - 1;
                upperRate = middleRate;

            } else {
//                System.out.println("此时的lower"+ lower + " 此时的upper" + upper);
                return middle;
            }

        }
        return -1;
    }

    /**
     * 递归法
     *
     * @param lower
     * @param upper
     * @param rate
     * @return 税率对应收入不存在返回-1， 否则返回先找到的一个值
     */
    public int overallByRecursion(int lower, int upper, double rate) {
        if (rate == 0.00d) {
            return 0;
        }
        if (rate >= 0.4d) {
            return -1;
        }
        return numeric(lower, upper, rate);
    }

    public int numeric(int lower, int upper, double rate) {
        int avg = (lower + upper) / 2;
        double avgRate = getRate(avg);
        if (avgRate == rate) {
            return avg;
        }
        if (avgRate < rate) {
            return numeric(avg, upper, rate);
        }
        return numeric(lower, avg, rate);
    }

}
